【综述专栏】深度学习模型大小与模型推理速度的探讨
在科学研究中,从方法论上来讲,都应“先见森林,再见树木”。当前,人工智能学术研究方兴未艾,技术迅猛发展,可谓万木争荣,日新月异。对于AI从业者来说,在广袤的知识森林中,系统梳理脉络,才能更好地把握趋势。为此,我们精选国内外优秀的综述文章,开辟“综述专栏”,敬请关注。
本文将对衡量深度学习模型大小的一些常用指标,如计算量、参数量、访存量、内存占用等进行探讨,分析这些指标对模型部署推理的影响,尤其是计算量与访存量对模型推理速度的影响,并给出在不同硬件架构下设计网络结构的一些建议。
00
当年头一次实习做算法的时候,主管给的第一个任务就是“把一个大的分割模型砍成一个小的”。当时并不理解模型“大”、“小”的真正含义,就简单的选取计算量作为评价指标,疯狂砍计算量(backbone 换 MobileNet/ShuffleNet、Conv 换成 DepthWise Conv、以及一些奇奇怪怪的融合结构等等),把模型计算量砍了将近 10 倍,结果一部署发现速度并没有快多少,反而是把最初的 ResNet 简单砍掉几个 block 效果更好。
也是从那时起接触了访存量、流水线、RoofLine 模型等概念,对模型推理速度的问题产生了兴趣,从此踏上了深度学习推理优化的不归路(划掉)。
如今做推理优化和 HPC 已经有一段时间了,还是偶尔能回想起当年不懂推理时设计的与硬件严重不匹配的模型。此外在工作中跟研究员沟通时,也会发现部分研究员对模型大小和模型推理速度的关系不太了解,设计出一些很难发挥硬件计算能力的模型结构。因此在这里对一些用于评价模型大小的指标——计算量、参数量、访存量、内存占用等指标进行详细探讨,分析这些指标会对模型的部署推理产生何种影响,详细讨论计算量和访存量对模型推理速度的影响,并给出不同硬件架构下设计高效网络结构的一些建议。
本文不仅仅是为了给出网络的设计建议,更是希望能够有效传达性能优化的基础理论知识,以及性能分析的基本思路,帮助各位同学减少网络设计与部署之间的 gap,更高效的完成网络设计与部署工作。非常希望本文能够对大家的工作有所帮助,也非常欢迎大家在评论区留言探讨。
01
目前常用于评价模型大小的指标有:计算量、参数量、访存量、内存占用等,这些指标从不同维度评价了模型的大小。本节仅作简单介绍,熟悉的小伙伴可以跳过此节,直接看后面的分析与探讨。
1. 计算量
计算量可以说是评价模型大小最常用的指标了,很多论文在跟 baseline 进行比较时,都会把计算量作为重要的比较依据。
计算量是模型所需的计算次数,反映了模型对硬件计算单元的需求。计算量一般用 OPs (Operations) ,即计算次数来表示。由于最常用的数据格式为 float32,因此也常常被写作 FLOPs (Floating Point Operations),即浮点计算次数。(这里为了跟传统习惯保持一致,下文就统一采用 FLOPs 啦)
模型的整体计算量等于模型中每个算子的计算量之和。而每个算子的计算量计算方法各不一致。例如对于 Eltwise Sum 来讲,两个大小均为 (N, C, H, W) 的 Tensor 相加,计算量就是 N x C x H x W;而对于卷积来说,计算量公式为(乘加各算一次):
2. 参数量
3. 访存量
4. 内存占用
5. 小结
02
答案是否定的。
实际上计算量和实际的推理速度之间没有直接的因果关系。计算量仅能作为模型推理速度的一个参考依据。
模型在特定硬件上的推理速度,除了受计算量影响外,还会受访存量、硬件特性、软件实现、系统环境等诸多因素影响,呈现出复杂的特性。因此,在手头有硬件且测试方便的情况下,实测是最准确的性能评估方式。
在设计网络结构时,如果有实测的条件,建议在模型迭代早期对性能也进行测试。一些 NAS 的方法也会对搜索出来的网络结构进行测速,或者干脆对硬件速度进行了建模,也作为初期搜索的重要参数。这种方法设计出来的网络在后期部署时,会极大减少因性能问题迭代优化的时间和人力开销。
这里我将讨论影响模型在硬件上推理速度的一些因素,一方面希望可以帮助手动/自动设计网络结构的同学更快的设计更高效的网络结构,另一方面希望当模型部署时性能出现问题时能够为大家提供分析原因的思路。
这一问题我将从如下 3 个点进行讨论:
计算密度与 RoofLine 模型
计算密集型算子与访存密集型算子
推理时间
1. 计算密度与 RoofLine 模型
计算密度是指一个程序在单位访存量下所需的计算量,单位是 FLOPs/Byte。其计算公式很简单,很多教材、资料里也称之为计算访存比,用于反映一个程序相对于访存来说计算的密集程度:
2. 计算密集型算子与访存密集型算子
3. 推理时间
4. 小结
03
RoofLine 模型可以用来评估程序的性能上界,但是实际能达到的性能还会受到硬件限制、系统环境、软件实现等诸多因素的影响,距离性能上界有一定距离。本章将对这些影响因素进行分析。
1. 硬件限制对性能上界的影响
前面 RoofLine 模型使用的峰值算力及内存带宽,是根据纸面数据计算得到的,是理论上的最大值。但在实际情况下,硬件会因为种种原因,无法达到这个理论值。因此建议大家对硬件进行micro-benchmark,以获取硬件的真实性能上限。
以上文的 Intel X86 CPU 为例,我们之前计算的 avx512 理论算力为 4.608 TFLOPs/s,但这个数值的前提是频率能维持在 4.5 GHz。然而实际上在使用 16 核跑 avx512 指令时,CPU 频率会下降到约 2.9 GHz,此时理论算力仅剩下 2.96 TFLOPs/s,而实测值仅有 2.86 TFLOPs/s。
除了频率之外,有些芯片可能会因为一些设计上或实现上的原因,导致在实际使用时达不到理论峰值。比如一些低端芯片不支持多发射、不支持乱序执行、采用了阻塞式 Cache 等等,一些芯片甚至会有一些性能 bug,导致在实际使用时几乎到达不了理论峰值(这里我个人倾向于把这些原因归结为硬件限制带来的损失)。
内存同理,该平台理论带宽为 96GB/s,但实测下来最高读带宽仅有 74 GB/s,仅能到达理论带宽的 77%。
我们可以得到修正后的 RoofLine 模型,图中蓝色填充部分反映了因实际算力和内存带宽达到不了理论值而造成的损失:
修正了实测峰值算力和内存带宽后的 RoofLine 模型,蓝色填充部分为硬件限制带来的损失
修正后的模型“拐点”发生了变化,因此算子的性质也会发生变化。建议拿到硬件后对硬件进行 micro-benchmark,这里推荐两个测试工具:
一个是高叔叔写的浮点峰值测试方法的文章,最后有 github 链接,大家可以 clone 下来测试硬件峰值:
https://zhuanlan.zhihu.com/p/28226956
还有一个是 stream 测试工具,可以用于测试内存带宽:
https://www.cs.virginia.edu/stream/
2. 系统环境对性能的影响
除非程序运行在裸机中,否则操作系统一定会对性能上界产生一定影响,比如操作系统在多核间的调度损失、操作系统的内存管理带来的损失、操作系统本身占用的运算资源等等。
对于一般的深度学习推理任务而言,现代操作系统对性能的影响并不是特别明显。但是在一些特殊情况下,也会带来严重的性能损失。我这里将会举两个例子:
一个是 Android 系统在大小核上的调度,一旦程序在 CPU 上的占用率不足(比如是周期工作的任务),则有可能被 Android 调度到小核上,带来性能损失。
另一个例子是内存缺页。在 Linux 系统上,当向系统申请内存页后,系统只是返回了虚拟页,等到程序实际使用虚拟页时,才会通过触发缺页异常的方式,进入操作系统内核分配物理页,这一过程会严重降低性能。
好在这些问题可以通过软件进行一部分弥补,例如调度问题可以使用绑核来解决,缺页问题可以通过绑定物理页(需要内核态)或内存池来解决。因此操作系统带来的影响是可控的。
除了操作系统带来的影响,系统中运行的其他进程也会对当前进程造成影响。比如一个系统中运行了多个深度学习实例,或者系统后台一些 APP 自启动了等等。这些进程都会占用核心算力和内存带宽,造成当前进程性能损失。
这往往会导致在工程测试环境下性能达标的模型,在实际部署时性能下降。因此,必须关注工程测试环境和实际部署系统环境的差异。如有条件,最好在实际部署环境下进行测试。
3. 软件实现对性能的影响
除了硬件限制和系统环境外,一个任务的软件实现好坏对性能有着重大的影响。
例如对于同样的矩阵操作任务,使用 python 写的多重 for 循环,和用 numpy 高度优化过的矩阵操作函数,性能可以差出 1~2 个数量级。
对于深度学习模型推理而言,推理框架对模型性能的影响主要体现在:是否充分利用了硬件的流水线资源、是否高效利用了硬件中的缓存、是否采用了时间复杂度更低的算法、是否解决了操作系统带来的性能损失(如上文的调度问题和内存缺页问题)、是否进行了正确高效的图优化等等。
由于影响因素很多,因此软件对性能的影响往往呈现出很强的非线性,导致在评估性能时很难给出一些普适性的结论,很多时候只能具体情况具体分析。(有的时候甚至有点玄学【捂脸)
例如同样计算量的向量四则运算和超越函数,后者往往会慢于前者的原因是很多硬件不支持超越函数的 SIMD 指令;再比如空洞卷积(dilated Conv)性能会弱于普通卷积的原因是前者对访存的利用不如后者高效等等。
在软件实现的影响下,RoofLine 模型的上界再次下降,达到图中的红线(真实的非线性可能会比我随手画的要复杂的多):
对于一些访存非常密集且访存 pattern 连续的算子,如 Concat、Eltwise Sum、ReLU、LeakyReLU、ReflectionPad 等,在 Tensor 数据量很大的情况下,软件实现的损失会非常小,正常情况下基本都能达到内存带宽实测上限;如果框架采用了融合策略的话,基本可以达到 0 开销。 对于 Conv/FC/Deconv 等算子,在计算密度很高的情况下,大多数框架是能够很接近算力峰值的。但对于计算密度不是特别高的 case,不同框架的表现不一,需要实测才能确定。不过从大趋势而言,都是计算密度越高,硬件的利用率越高的。 尽量使用常用的算子参数,例如 Conv 尽量使用 3x3_s1/s2,1x1___s1/s2 等,这些常用参数往往会被特殊优化,性能更好。
4. 小结
04
前面讨论了一大堆,其实最实用的还是“怎么设计模型能够达到更快的推理速度”。
在给出我的个人建议之前,首先要先声明的是:由于不同硬件、不同环境、不同框架的差异会很大,这些建议可能并不是在所有条件下都适用。在设计算法或性能测试遇到疑问时,建议咨询部署/优化的同学。
好了,废话不多说(其实已经说了很多了),给出我的一些个人建议:
方法论建议:
了解目标硬件的峰值算力和内存带宽,最好是实测值,用于指导网络设计和算子参数选择。
明确测试环境和实际部署环境的差异,最好能够在实际部署环境下测试性能,或者在测试环境下模拟实际部署环境。
针对不同的硬件平台,可以设计不同计算密度的网络,以在各个平台上充分发挥硬件计算能力(虽然工作量可能会翻好几倍【捂脸)。
除了使用计算量来表示/对比模型大小外,建议引入访存量、特定平台执行时间,来综合反映模型大小。
实测是最准确的性能评估方式,如果有条件快速实测的话,建议以实测与理论分析相结合的方式设计并迭代网络。
遇到性能问题时,可以逐层 profiling,并与部署/优化同学保持紧密沟通,具体问题具体分析(适当了解一下计算相关理论的话,可以更高效的沟通)。
网络设计建议:
对于低算力平台(CPU、低端 GPU 等),模型很容易受限于硬件计算能力,因此可以采用计算量低的网络来降低推理时间。
对于高算力平台(GPU、DSP 等),一味降低计算量来降低推理时间就并不可取了,往往更需要关注访存量。单纯降低计算量,很容易导致网络落到硬件的访存密集区,导致推理时间与计算量不成线性关系,反而跟访存量呈强相关(而这类硬件往往内存弱于计算)。相对于低计算密度网络而言,高计算密度网络有可能因为硬件效率更高,耗时不变乃至于更短。
面向推理性能设计网络结构时,尽量采用经典结构,大部分框架会对这类结构进行图优化,能够有效减少计算量与访存量。例如 Conv->BN->ReLU 就会融合成一个算子,但 Conv->ReLU->BN 就无法直接融合 BN 层
算子的参数尽量使用常用配置,如 Conv 尽量使用 3x3_s1/s2、1x1___s1/s2 等,软件会对这些特殊参数做特殊优化。
CNN 网络 channel 数尽量选择 4/8/16/32 的幂次,很多框架的很多算子实现在这样的 channel 数下效果更好(具体用多少不同平台不同框架不太一样)。
框架除了计算耗时外,也处理网络拓扑、内存池、线程池等开销,这些开销跟网络层数成正比。因此相比于“大而浅”的网络,“小而深”的网络这部分开销更大。一般情况下这部分开销占比不大。但在网络算子非常碎、层数非常多的时候,这部分开销有可能会影响多线程的扩展性,乃至于成为不可忽视的耗时因素。
一些其他建议:
除了优化网络结构、推理框架性能外,还可以考虑通过一些其他工程技巧来提升系统整体的性能。例如:对推理服务流水化,并行数据读取与计算的过程,掩盖 IO 延时。
本文介绍了评估模型大小的四个常用指标——计算量、参数量、访存量、内存占用,从 RoofLine 模型入手详细讨论了影响模型推理速度的影响因素,并给出了面向推理速度的模型设计方法论与建议。
撰写本文的目的,不仅仅是给算法同学提供有效的网络设计建议,更多的还是希望能够传达性能优化的基础知识与分析思路,减少算法设计到部署之间的 gap,更快速高效的设计推理友好的网络模型。希望能对大家的工作有所帮助。
由于本人知识水平有限,如有错误和不详尽的地方,望大家不吝指出,非常欢迎大家在评论区留言探讨。
本文目的在于学术交流,并不代表本公众号赞同其观点或对其内容真实性负责,版权归原作者所有,如有侵权请告知删除。
“综述专栏”历史文章
一文了解推荐系统中的图神经网络
自编码器的最佳特征:最大化互信息
系统了解Encoder-Decoder 和 Seq2Seq及其关键技术
GNN解耦表征论文汇总
最简单的self-supervised方法
Transformer结构理解及一些细节!
识别与诊断--基于深度学习的计算病理学进阶应用
BERT知识蒸馏综述
More About Attention
NLP词向量发展历程
基于深度学习的有监督关系抽取方法简介
AI系统安全的实用方法
一篇综述带你全面了解领域泛化(Domain Generalization)
到底什么是生成式对抗网络GAN?
DeepGNN: 图神经网络如何变深
更多综述专栏文章,
请点击文章底部“阅读原文”查看
分享、点赞、在看,给个三连击呗!